package org.sfx.rich

import java.lang.reflect.Constructor
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.lang.reflect.TypeVariable
import javafx.beans.value.ObservableValue
import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.ObjectProperty
import javafx.collections.ObservableList
import org.sfx.rich.Enrich._

case class RichClass[T](underlying: Class[T]) {
  type Underlying <: Class[T]
  def apply(): T = underlying.newInstance()
  def isInstance(obj: Any): Boolean = underlying.isInstance(obj)
  def isAssignableFrom(clazz: Class[_]): Boolean = underlying.isAssignableFrom(clazz)
  def isInterface: Boolean = underlying.isInterface()
  def isArray: Boolean = underlying.isArray()
  def isPrimitive: Boolean = underlying.isPrimitive()
  def isAnnotation: Boolean = underlying.isAnnotation()
  def isSynthetic: Boolean = underlying.isSynthetic()
  def name: String = underlying.getName()
  def classLoader: ClassLoader = underlying.getClassLoader()
  def superClass: Option[Class[_ >: T]] = Option(underlying.getSuperclass())
  def genericSuperClass: Option[Type] = Option(underlying.getGenericSuperclass())
  def _package: Package = underlying.getPackage()
  def interfaces: Array[Class[_]] = underlying.getInterfaces()
  def genericInterfaces: Array[Type] = underlying.getGenericInterfaces()
  def componentType: Class[_] = underlying.getComponentType()
  def modifiers: Int = underlying.getModifiers()
  def signers: Array[Object] = underlying.getSigners()
  def enclosingMethod: Method = underlying.getEnclosingMethod()
  def enclosingConstructor: Constructor[_] = underlying.getEnclosingConstructor()
  def declaringClass: Class[_] = underlying.getDeclaringClass()
  def enclosingClass: Class[_] = underlying.getEnclosingClass()
  def simpleName: String = underlying.getSimpleName()
  def canonicalName: String = underlying.getCanonicalName()
  def isAnonymousClass: Boolean = underlying.isAnonymousClass()
  def isLocalClass: Boolean = underlying.isLocalClass()
  def isMemberClass: Boolean = underlying.isMemberClass()
  def classes: Array[Class[_]] = underlying.getClasses()
  def fields: Array[Field] = underlying.getFields()
  def methods: Array[Method] = underlying.getMethods()
  def constructors: Array[Constructor[_]] = underlying.getConstructors()
  def field(name: String): Field = underlying.getField(name)
  def method(name: String, parameterTypes: Class[_]*): Method = underlying.getMethod(name, parameterTypes: _*)
  def constructor(parameterTypes: Class[_]*): Constructor[_] = underlying.getConstructor(parameterTypes: _*)
  def declaredClasses: Array[Class[_]] = underlying.getDeclaredClasses()
  def declaredFields: Array[Field] = underlying.getDeclaredFields()
  def declaredMethods: Array[Method] = underlying.getDeclaredMethods()
  def declaredConstructors: Array[Constructor[_]] = underlying.getDeclaredConstructors()
  def declaredField(name: String): Field = underlying.getDeclaredField(name)
  def declaredMethod(name: String, parameterTypes: Class[_]*): Method = underlying.getDeclaredMethod(name, parameterTypes: _*)
  def declaredConstructor(parameterTypes: Class[_]*): Constructor[_] = underlying.getDeclaredConstructor(parameterTypes: _*)
  def resourcesAsStream(name: String) = underlying.getResourceAsStream(name)
  def resource(name: String) = underlying.getResource(name)
  def protectionDomain = underlying.getProtectionDomain()
  def desiredAssertionStatus = underlying.desiredAssertionStatus()
  def isEnum = underlying.isEnum()
  def enumConstants = underlying.getEnumConstants()
  def cast[T](obj: Any) = underlying.cast(obj)
  def asSubClass[U](clazz: Class[U]): Class[_ <: U] = underlying.asSubclass(clazz)
  
  lazy val cache = new Object {
    lazy val fields = RichClass.this.fields.map { field => field.name -> field }.toList.toMap
    lazy val methods = underlying.getMethods()
    lazy val properties = methods.flatMap { method =>
      if (classOf[ObservableValue[_]].isAssignableFrom(method.returnType)) {
        Some(method)
      } else {
        None
      }
    }
    lazy val observableLists = methods.flatMap { method =>
      if (classOf[ObservableList[_]].isAssignableFrom(method.returnType)) {
        Some(method)
      } else {
        None
      }
    }
    lazy val getters: Map[String, Method] = methods.flatMap { method =>
      method.getterName.map { _ -> method }
    }.toMap
    lazy val setters: Map[String, Method] = methods.flatMap { method =>
      method.setterName.map { _ -> method }
    }.toMap
  }
  
  def richKind: String = "Class"
}

object RichClass {
  def apply(className: String): Class[_] = Class.forName(className)

  def apply(name: String, initialize: Boolean, loader: ClassLoader): Class[_] = {
    Class.forName(name, initialize, loader)
  }
  
  trait A {
    def value: SimpleStringProperty
  }
  
  trait B {
    def value: ObjectProperty[String]
  }
  
  trait C {
    def value: ObservableValue[String]
  }
  
  def main(args: Array[String]): Unit = {
    /*val aClass = classOf[A]
    val bClass = classOf[B]
    val cClass = classOf[C]
    val aMethod = aClass.method("value")
    val bMethod = bClass.method("value")
    val cMethod = cClass.method("value")
    val aReturn: Type = aMethod.genericReturnType
    val bReturn: Type = bMethod.genericReturnType
    val cReturn: Type = cMethod.genericReturnType
    val aReturnClass: Class[_] = aReturn.asInstanceOf[Class[_]]
    val bReturnClass: ParameterizedType = bReturn.asInstanceOf[ParameterizedType]
    val cReturnClass: ParameterizedType = cReturn.asInstanceOf[ParameterizedType]
    println(aReturn.richKind)
    println(bReturn.richKind)
    println(cReturn.richKind)
    println(aReturnClass.richKind)
    println(bReturnClass.richKind)
    println(cReturnClass.richKind)*/
  }
}
